
#include <GL/glut.h>    // Header File For The GLUT Library 
#include <GL/gl.h>	// Header File For The OpenGL32 Library
#include <GL/glu.h>	// Header File For The GLu32 Library
#include <unistd.h>     // Header File For sleeping.
#include <stdio.h>
#include <string.h>
#include "../libcg/cg.h"
#include "../libcg/utils.h"
#include "../libcg/bezier.h"
#include "../libcg/diffuse.h"

#define ESCAPE 27

int window; 

#define GRID_SIZE1 10
#define GRID_SIZE2 50

#define ANGLE_STEP 0.01
#define ZPOS_STEP 0.1

#define INIT_ZPOS -6.0
#define INIT_ANGLE 0.0

double angle_x, angle_y, zpos;
cgPoint **src_hmap, **bez_hmap;
TransformMatrix tm;
int need_redraw;
double tm_gl[16];
double light_pos[] = {0.0, -1.0, 0.0};
double light[] = {1.0, 1.0, 1.0};
double light_intens = 0.7;
double kd = 1.0;
double ambient = 0.1;

void reset_state(void)
{
    int i;

    src_hmap = (cgPoint **) malloc(GRID_SIZE1 * sizeof(cgPoint*));
    for (i = 0; i < GRID_SIZE1; i++)
	src_hmap[i] = (cgPoint *) malloc(GRID_SIZE1 * sizeof(cgPoint));

    bez_hmap = (cgPoint **) malloc(GRID_SIZE2 * sizeof(cgPoint*));
    for (i = 0; i < GRID_SIZE2; i++)
	bez_hmap[i] = (cgPoint *) malloc(GRID_SIZE2 * sizeof(cgPoint));

    cg_random_grid(src_hmap, GRID_SIZE1, GRID_SIZE1, 
		   -2.0, 2.0, -2.0, 2.0, -1.0, 1.0);
    cg_bezier_grid(bez_hmap, src_hmap, GRID_SIZE1);
    
    angle_x = angle_y = INIT_ANGLE;
    zpos = INIT_ZPOS;
    need_redraw = 1;
}

void init_gl(int Width, int Height)
// We call this right after our OpenGL window is created.
{
    glShadeModel(GL_SMOOTH);    
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClearDepth(1.0);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    glLineWidth(2.0);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(45.0f, (GLfloat)Width/(GLfloat)Height, 0.1f, 100.0f);

    glMatrixMode(GL_MODELVIEW);
    cg_srand();
    reset_state();
}

void resize_gl_scene(int width, int height)
{
    if (height==0)  // Prevent A Divide By Zero If The Window Is Too Small
	height=1;

    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
    glMatrixMode(GL_MODELVIEW);
}

void draw_axis(void)
{
    glBegin(GL_LINES);
    glColor3f(1.0, 0.0, 0.0);
    glVertex3f(0.0f, 0.0f, 0.0f);
    glVertex3f(1.0f, 0.0f, 0.0f);

    glColor3f(0.0, 1.0, 0.0);
    glVertex3f(0.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 1.0f, 0.0f);

    glColor3f(0.0, 0.0, 1.0);
    glVertex3f(0.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 0.0f, 1.0f);
    glEnd();
}

void draw_surf()
{
    int i, j;
    double color = 1.0;
    //glColor3f(0.0f, 0.0f, 0.0f);
    glBegin(GL_TRIANGLES);
    for (i = 0; i < GRID_SIZE2-1; i++)
    	for (j = 0; j < GRID_SIZE2-1; j++) {
	    color = calc_diffuse_intens(bez_hmap[i]+j, bez_hmap[i+1]+j, bez_hmap[i]+j+1, light_pos, light_intens, kd) + ambient;
    	    //glColor3f(1.0, 1.0, bez_hmap[i][j].c[2]);
	    glColor3f(color, color, color);
    	    glVertex3f(bez_hmap[i][j].c[0], bez_hmap[i][j].c[1], bez_hmap[i][j].c[2]);
    	    //glColor3f(1.0, 1.0, bez_hmap[i+1][j].c[2]);
    	    glVertex3f(bez_hmap[i+1][j].c[0], bez_hmap[i+1][j].c[1], bez_hmap[i+1][j].c[2]);
    	    //glColor3f(1.0, 1.0, bez_hmap[i][j+1].c[2]);
    	    glVertex3f(bez_hmap[i][j+1].c[0], bez_hmap[i][j+1].c[1], bez_hmap[i][j+1].c[2]);
            color = calc_diffuse_intens(bez_hmap[i]+j+1, bez_hmap[i+1]+j, bez_hmap[i+1]+j+1, light_pos, light_intens, kd) + ambient;
    	    //glColor3f(1.0, 1.0, bez_hmap[i][j].c[2]);
	    glColor3f(color, color, color);
            //glColor3f(1.0, 1.0, bez_hmap[i][j+1].c[2]);
    	    glVertex3f(bez_hmap[i][j+1].c[0], bez_hmap[i][j+1].c[1], bez_hmap[i][j+1].c[2]);
            //glColor3f(1.0, 1.0, bez_hmap[i+1][j].c[2]);
    	    glVertex3f(bez_hmap[i+1][j].c[0], bez_hmap[i+1][j].c[1], bez_hmap[i+1][j].c[2]);
    	    //glColor3f(1.0, 1.0, bez_hmap[i+1][j+1].c[2]);
    	    glVertex3f(bez_hmap[i+1][j+1].c[0], bez_hmap[i+1][j+1].c[1], bez_hmap[i+1][j+1].c[2]);
    	}
    glEnd();
    glBegin(GL_LINES);
    glColor3f(0.0, 1.0, 0.0);
    for (i = 0; i < GRID_SIZE1-1; i++)
    {
    	for (j=0; j < GRID_SIZE1-1; j++) {
            glVertex3f(src_hmap[i][j].c[0], src_hmap[i][j].c[1], src_hmap[i][j].c[2]);
            glVertex3f(src_hmap[i+1][j].c[0], src_hmap[i+1][j].c[1], src_hmap[i+1][j].c[2]);
	    
	    glVertex3f(src_hmap[i+1][j].c[0], src_hmap[i+1][j].c[1], src_hmap[i+1][j].c[2]);
    	    glVertex3f(src_hmap[i+1][j+1].c[0], src_hmap[i+1][j+1].c[1], src_hmap[i+1][j+1].c[2]);
	    
	    glVertex3f(src_hmap[i+1][j+1].c[0], src_hmap[i+1][j+1].c[1], src_hmap[i+1][j+1].c[2]);
    	    glVertex3f(src_hmap[i][j+1].c[0], src_hmap[i][j+1].c[1], src_hmap[i][j+1].c[2]);
	    
	    glVertex3f(src_hmap[i][j+1].c[0], src_hmap[i][j+1].c[1], src_hmap[i][j+1].c[2]);
            glVertex3f(src_hmap[i][j].c[0], src_hmap[i][j].c[1], src_hmap[i][j].c[2]);
	    
	    /* glVertex3f(src_hmap[i][j].c[0], src_hmap[i][j].c[1], src_hmap[i][j].c[2]); */
    	    /* glVertex3f(src_hmap[i+1][j+1].c[0], src_hmap[i+1][j+1].c[1], src_hmap[i+1][j+1].c[2]); */
    	}
    }
    glEnd();
}

/* The main drawing function. */
void draw_gl_scene()
{
    // Clear The Screen And The Depth Buffer
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();  // Reset The View

    glTranslated(0.0, 0.0, zpos);
    draw_axis();
    if (need_redraw) {
	cg_identity_matrix(&tm);
        cg_rotate(&tm, X, angle_x);
        cg_rotate(&tm, Y, angle_y);
        //cg_matrix_to_opengl(tm_gl, &tm);
	//glMultMatrixd(tm_gl);
        cg_transform_grid(bez_hmap, GRID_SIZE2, GRID_SIZE2, &tm);
        cg_transform_grid(src_hmap, GRID_SIZE1, GRID_SIZE1, &tm);
	angle_x = angle_y = 0.0;
	need_redraw = 0;
    }
    draw_surf();

    // swap buffers to display, since we're double buffered.
    glutSwapBuffers();
}

/* The function called whenever a key is pressed. */
void key_pressed(unsigned char key, int x, int y) 
{
    int i;
    /* avoid thrashing this procedure */
    usleep(100);

    /* If escape is pressed, kill everything. */
    switch (key) { 
    case ESCAPE:
	for (i = 0; i < GRID_SIZE2; i++)
	    free(bez_hmap[i]);
	free(bez_hmap);
        for (i = 0; i < GRID_SIZE1; i++)
            free(src_hmap[i]);
        free(src_hmap);
        /* shut down our window */
	glutDestroyWindow(window); 
	/* exit the program...normal termination. */
	exit(0);
	break;
    case 'r':
	reset_state();
	break;
    case 'w':
	zpos += ZPOS_STEP;
	break;
    case 's':
	zpos -= ZPOS_STEP;
	break;
    /* case 'x': case 'y': case 'z': */
    /* 	axis = key - 'x';  // look at axis defines at top of file */
    /* 	break; */
    }
}

void special_key_pressed(int key, int x, int y) 
{
    switch(key) {
    case GLUT_KEY_DOWN: case GLUT_KEY_UP:
	angle_x = (key == GLUT_KEY_UP ? ANGLE_STEP : -ANGLE_STEP);
	need_redraw = 1;
	break;
    case GLUT_KEY_LEFT: case GLUT_KEY_RIGHT:
	angle_y = (key == GLUT_KEY_LEFT ? ANGLE_STEP : -ANGLE_STEP);
	need_redraw = 1;
	break;
    }
}

int main(int argc, char **argv) 
{  
    glutInit(&argc, argv);  

    /* Select type of Display mode:   
       Double buffer 
       RGBA color
       Alpha components supported 
       Depth buffer */  
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH);  

    /* get a 640 x 480 window */
    glutInitWindowSize(640, 480);  

    /* the window starts at the upper left corner of the screen */
    glutInitWindowPosition(0, 0);  

    /* Open a window */  
    window = glutCreateWindow("Rotate");  

    /* Register the function to do all our OpenGL drawing. */
    glutDisplayFunc(&draw_gl_scene);  

    /* Go fullscreen.  This is the soonest we could possibly go fullscreen. */
    //glutFullScreen();

    /* Even if there are no events, redraw our gl scene. */
    glutIdleFunc(&draw_gl_scene);

    /* Register the function called when our window is resized. */
    glutReshapeFunc(&resize_gl_scene);

    /* Register the function called when the keyboard is pressed. */
    glutKeyboardFunc(&key_pressed);
    glutSpecialFunc(&special_key_pressed);

    /* Initialize our window. */
    init_gl(640, 480);
  
    /* Start Event Processing Engine */  
    glutMainLoop();  

    return 1;
}
